package com.tools.middle.redis.client;

import com.fasterxml.jackson.core.type.TypeReference;
import com.tools.common.container.AbsOwnList;
import com.tools.common.container.OwnPairs;
import com.tools.common.container.RowKit;
import com.tools.common.object.C;
import com.tools.common.object.json.JsonKit;
import com.tools.common.object.Note;
import com.tools.common.object.ObjKit;
import com.tools.common.object.string.VerifyKit;
import com.tools.common.thread.Timeunit;
import redis.clients.jedis.*;

import java.util.*;

/**
 * Jedis 的抽象实现
 * 自定义了一些更适用的方法
 * */
@Note("Jedis 的抽象实现" +
        "自定义了一些更适用的方法")
abstract class AbsJedis implements AutoCloseable {

    AbsJedis() {}

    @Note("获取本线程的 Jedis 副本。实现是一个 ThreadLocal<Jedis>")
    protected abstract Jedis jedis();

    @Note("回收资源")
    public abstract void close();


    /* **********************************************************************************
     *
     *          原名称方法，用完自动 remove 和 close
     *
     * *********************************************************************************
     * */

    @Note("返回批处理管道，将多个 Redis 命令打包在一起一次性发送到服务器执行。" +
            "注意批量命令的执行是 【非原子性】 的。" +
            "Pipeline 的详细使用：执行命令后，调用 sync() 方法真正执行命令。然后用 response.get() 获取结果。" +
            "最后 Pipeline 需要被 close()")
    public Pipeline pipeline() {
        return this.jedis().pipelined();
    }

    @Note("开启 Redis 事务，可使用 Transaction 对象操作 Redis")
    public Transaction multi() {
        return this.jedis().multi();
    }

    @Note("Redis的 ttl 命令，返回该 key 的剩余过期时间，单位为秒")
    public long ttl(String key) {
        DataTypeKit.default_checkKey(key);
        Long ttl = this.jedis().ttl(key);
        return DataTypeKit.default_returnTimeResult(ttl);
    }

    @Note("Redis的 ttl 命令，返回该 key 的剩余过期时间，并转为指定的单位")
    public long ttl(String key, Timeunit timeunit) {
        DataTypeKit.default_checkTimeunit(timeunit);
        long ttl = this.ttl(key);
        return (ttl < 1) ? ttl : timeunit.cast(ttl, Timeunit.SECOND);
    }

    @Note("给指定的 key 设置过期时间，单位为秒。为 0 或负数则永不过期。")
    public long expired(String key, long expiration, Timeunit timeunit) {
        DataTypeKit.default_checkTimeunit(timeunit);
        return this.expired(key, Timeunit.toSecond(expiration, timeunit));
    }

    @Note("给指定的 key 设置过期时间，单位为秒。为 0 或负数则永不过期。")
    public long expired(String key, long seconds) {
        DataTypeKit.default_checkKey(key);
        Long result = this.jedis().expire(key, seconds);
        return DataTypeKit.default_returnTimeResult(result);
    }

    public long del(String key) {
        DataTypeKit.default_checkKey(key);
        Long delResult = this.jedis().del(key);
        return DataTypeKit.default_returnResultLong(delResult);
    }


    public long del(String... keys) {
        DataTypeKit.default_checkKeys(keys);
        Long delResult = this.jedis().del(keys);
        return DataTypeKit.default_returnResultLong(delResult);
    }

    public long del(Iterable<String> keys) {
        String[] keyArray = DataTypeKit.default_asStringKeyArray(keys);
        Long delResult = this.jedis().del(keyArray);
        return DataTypeKit.default_returnResultLong(delResult);
    }


    @Note("替换 key 的名称")
    public String rename(String oldKey, String newKey) {
        DataTypeKit.default_checkKey(oldKey);
        DataTypeKit.default_checkKey(newKey);
        return this.jedis().rename(oldKey, newKey);
    }

    @Note("随机返回一个 key")
    public String randomKey() {
        return this.jedis().randomKey();
    }


    /* *********************************************************************
     *
     *          字符串操作
     *
     * *********************************************************************
     * */


    public String set(String key, Object value) {
        DataTypeKit.default_checkKey(key);
        return this.jedis().set(key, ObjKit.toStringEmptyString(value));
    }

    public String setex(String key, long seconds, Object value) {
        DataTypeKit.default_checkKey(key);
        return this.jedis().setex(key, seconds, ObjKit.toStringEmptyString(value));
    }

    public String setex(String key, long expiration, Timeunit timeunit, Object value) {
        DataTypeKit.default_checkKey(key);
        DataTypeKit.default_checkTimeunit(timeunit);
        return this.jedis().setex(key, Timeunit.toSecond(expiration, timeunit), ObjKit.toStringEmptyString(value));
    }

    @Note("如果不存在就 set")
    public long setnx(String key, Object value) {
        DataTypeKit.default_checkKey(key);
        Long result = this.jedis().setnx(key, ObjKit.toStringEmptyString(value));
        return DataTypeKit.default_returnResultLong(result);
    }

    @Note("返回 value 的 length")
    public long strlen(String key) {
        DataTypeKit.default_checkKey(key);
        Long result = this.jedis().strlen(key);
        return DataTypeKit.default_returnResultLong(result);
    }

    public Object get(String key) {
        DataTypeKit.default_checkKey(key);
        String result = this.jedis().get(key);
        return ObjKit.stringToRealValue(result, true);
    }

    public <T>T get(String key, Class<T> targetType) {
        DataTypeKit.default_checkKey(key);
        String result = this.jedis().get(key);
        return JsonKit.toEntity(result, targetType);
    }

    public <T>T get(String key, TypeReference<T> targetType) {
        DataTypeKit.default_checkKey(key);
        String result = this.jedis().get(key);
        return JsonKit.toEntity(result, targetType);
    }

    public <T>T get(String key, C<T> targetType) {
        DataTypeKit.default_checkKey(key);
        String result = this.jedis().get(key);
        return (T) ObjKit.toRealValue(result, targetType);
    }

    @Note("如果 key 不存在就 set 新值 newValue，然后返回 newValue。存在则返回旧值")
    public Object getSet(String key, Object newValue) {
        DataTypeKit.default_checkKey(key);
        String res = this.jedis().getSet(key, ObjKit.toStringEmptyString(newValue));
        return ObjKit.stringToRealValue(res, true);
    }

    public <T>T getSet(String key, Object newValue, Class<T> valueType) {
        DataTypeKit.default_checkKey(key);
        String res = this.jedis().getSet(key, ObjKit.toStringEmptyString(newValue));
        return JsonKit.toEntity(res, valueType);
    }

    public <T>T getSet(String key, Object newValue, TypeReference<T> valueType) {
        DataTypeKit.default_checkKey(key);
        String result = this.jedis().getSet(key, ObjKit.toStringEmptyString(newValue));
        return JsonKit.toEntity(result, valueType);
    }

    public <T>T getSet(String key, Object newValue, C<T> valueType) {
        DataTypeKit.default_checkKey(key);
        String result = this.jedis().getSet(key, ObjKit.toStringEmptyString(newValue));
        return (T) ObjKit.stringToRealValue(result, valueType);
    }

    public List<Object> mget(String... keys) {
        DataTypeKit.default_checkKeys(keys);
        List<String> res = this.jedis().mget(keys);
        return DataTypeKit.default_asObjectList(res);
    }

    public <T> List<T> mget(Class<T> valueType, String... keys) {
        DataTypeKit.default_checkKeys(keys);
        List<String> res = this.jedis().mget(keys);
        return DataTypeKit.default_asSpecifyTypeList(res, valueType);
    }

    public <T> List<T> mget(C<T> valueType, String... keys) {
        DataTypeKit.default_checkKeys(keys);
        List<String> res = this.jedis().mget(keys);
        return DataTypeKit.default_asSpecifyTypeList(res, valueType);
    }

    public List<Object> mget(Iterable<String> keys) {
        String[] keyArray = DataTypeKit.default_asStringKeyArray(keys);
        List<String> res = this.jedis().mget(keyArray);
        return DataTypeKit.default_asObjectList(res);
    }

    public <T> List<T> mget(Class<T> valueType, Iterable<String> keys) {
        String[] keyArray = DataTypeKit.default_asStringKeyArray(keys);
        List<String> res = this.jedis().mget(keyArray);
        return DataTypeKit.default_asSpecifyTypeList(res, valueType);
    }

    public <T> List<T> mget(C<T> valueType, Iterable<String> keys) {
        String[] keyArray = DataTypeKit.default_asStringKeyArray(keys);
        List<String> res = this.jedis().mget(keyArray);
        return DataTypeKit.default_asSpecifyTypeList(res, valueType);
    }


    public Map<String, Object> mgetAsMap(String... keys) {
        DataTypeKit.default_checkKeys(keys);
        List<String> res = this.jedis().mget(keys);
        return DataTypeKit.default_hashResultAsMap(keys, res);
    }

    public Map<String, Object> mgetAsMap(Iterable<String> keys) {
        String[] keyArray = DataTypeKit.default_asStringKeyArray(keys);
        List<String> res = this.jedis().mget(keyArray);
        return DataTypeKit.default_hashResultAsMap(keyArray, res);
    }

    public String mset(Map<String, Object> keyAndValue) {
        String[] param = DataTypeKit.default_asKeyValueStringArray(keyAndValue);
        return this.jedis().mset(param);
    }

    public String mset(OwnPairs<String, Object> keyAndValue) {
        String[] param = DataTypeKit.default_asKeyValueStringArray(keyAndValue);
        return this.jedis().mset(param);
    }

    public String mset(Object javaBean) {
        if(javaBean == null) throw new NullPointerException("JavaBean 对象为 null");
        Map<String, Object> map = ObjKit.allFieldValue(javaBean);
        return this.mset(map);
    }

    public String mset(String jsonObject) {
        if(!VerifyKit.isJsonObject(jsonObject)) throw new IllegalArgumentException(jsonObject + " 不是对象类型的 JSON 字符串");
        Map<String, Object> map = JsonKit.toEntity(jsonObject,
                new TypeReference<HashMap<String, Object>>() {});
        return this.mset(map);
    }


    @Note("游标 cursor 的初始值为【0】，当返回的游标值再度为【0】 时才代表整体遍历结束" +
            "返回值是批量的 key 和下一次开始游标。" +
            "返回的结果可能会有重复，需要自己去重")
    public ScanResult<String> scan(String cursor) {
        if(cursor == null || cursor.isEmpty()) cursor = "0";
        return this.jedis().scan(cursor);
    }

    @Note("ScanParams 是可以设置 count（返回 key 的个数）和 match（正则匹配）的")
    public ScanResult<String> scan(String cursor, ScanParams params) {
        if(cursor == null || cursor.isEmpty()) cursor = "0";
        return (params == null)
                ? scan(cursor)
                : this.jedis().scan(cursor, params);
    }


    public ScanResult<String> scan(String cursor, ScanParams params, String type) {
        if(cursor == null || cursor.isEmpty()) cursor = "0";
        if(params == null) return scan(cursor);
        if(type == null || type.isEmpty()) return scan(cursor, params);
        return this.jedis().scan(cursor, params, type);
    }

    @Note("执行 lua 脚本。可使 redis 的多个操作为原子性操作")
    public Object eval(String luaScript) {
        DataTypeKit.default_checkLuaScriptIsNotEmpty(luaScript);
        return this.jedis().eval(luaScript);
    }

    @Note("执行 lua 脚本。参数是告诉 redis 从 0 到 keyCount 个参数是 key，剩余的全是参数")
    public Object eval(String luaScript, int keyCount, Object... args) {
        DataTypeKit.default_checkLuaScriptIsNotEmpty(luaScript);
        String[] keyAndArgs = DataTypeKit.default_asStringArray(args);
        return this.jedis().eval(luaScript, keyCount, keyAndArgs);
    }

    @Note("执行 lua 脚本。参数是告诉 redis 从 0 到 keyCount 个参数是 key，剩余的全是参数")
    public Object eval(String luaScript, int keyCount, List args) {
        DataTypeKit.default_checkLuaScriptIsNotEmpty(luaScript);
        String[] keyAndArgs = DataTypeKit.default_asStringArray(args);
        return this.jedis().eval(luaScript, keyCount, keyAndArgs);
    }

    @Note("执行 lua 脚本。参数是告诉 redis 从 0 到 keyCount 个参数是 key，剩余的全是参数")
    public Object eval(String luaScript, int keyCount, AbsOwnList args) {
        DataTypeKit.default_checkLuaScriptIsNotEmpty(luaScript);
        String[] keyAndArgs = DataTypeKit.default_asStringArray(args);
        return this.jedis().eval(luaScript, keyCount, keyAndArgs);
    }

    @Note("执行 lua 脚本，返回值可能是【String、Long、List<Object>、null" +
            "或 【布尔值的（0 或 1，也是用 Long 接收）】")
    public Object eval(String luaScript, List<String> keys, List args) {
        DataTypeKit.default_checkLuaScriptIsNotEmpty(luaScript);
        List<String> params = new ArrayList<>(args.size());
        DataTypeKit.default_copyToStringCollection(params, args);
        return this.jedis().eval(luaScript, keys, params);
    }

    public Object eval(String luaScript, String[] keys, Object... args) {
        DataTypeKit.default_checkLuaScriptIsNotEmpty(luaScript);
        List<String> keyArray = RowKit.toArrayList(keys);
        List<String> params = new ArrayList<>(args.length);
        DataTypeKit.default_copyToStringCollection(params, args);
        return this.jedis().eval(luaScript, keyArray, params);
    }


    public Object eval(String luaScript, AbsOwnList<String> keys, AbsOwnList args) {
        DataTypeKit.default_checkLuaScriptIsNotEmpty(luaScript);
        List<String> params = new ArrayList<>(args.size());
        DataTypeKit.default_copyToStringCollection(params, args);
        return this.jedis().eval(luaScript, keys.toList(), params);
    }


    /* **********************************************************************************
     *
     *          带 【$】 符合的方法，如果不是在项目的请求中使用，需要手动调用 close 方法关闭
     *
     * *********************************************************************************
     * */

    public long ttl$(String key) {
        try {
            return this.ttl(key);
        } finally {
            this.close();
        }
    }

    public long ttl$(String key, Timeunit timeunit) {
        try {
            return this.ttl(key, timeunit);
        } finally {
            this.close();
        }
    }

    public long expired$(String key, long expiration, Timeunit timeunit) {
        try {
            return this.expired(key, expiration, timeunit);
        } finally {
            this.close();
        }
    }

    public long expired$(String key, long seconds) {
        try {
            return this.expired(key, seconds);
        } finally {
            this.close();
        }
    }

    public long del$(String key) {
        try {
            return this.del(key);
        } finally {
            this.close();
        }
    }

    public long del$(String... key) {
        try {
            return this.del(key);
        } finally {
            this.close();
        }
    }


    public long del$(Iterable<String> key) {
        try {
            return this.del(key);
        } finally {
            this.close();
        }
    }

    @Note("替换 key 的名称")
    public String rename$(String oldKey, String newKey) {
        try {
            return this.rename(oldKey, newKey);
        } finally {
            this.close();
        }
    }


    @Note("随机返回一个 key")
    public String randomKey$() {
        try {
            return this.randomKey();
        } finally {
            this.close();
        }
    }

    /* **********************************************************************************
     *
     *          字符串
     *
     * *********************************************************************************
     * */


    public String set$(String key, Object value) {
        try {
            return this.set(key, value);
        } finally {
            this.close();
        }
    }


    public String setex$(String key, long second, Object value) {
        try {
            return this.setex(key, second, value);
        } finally {
            this.close();
        }
    }

    public String setex$(String key, long expiration, Timeunit timeunit, Object value) {
        try {
            return this.setex(key, expiration, timeunit, value);
        } finally {
            this.close();
        }
    }

    public long setnx$(String key, Object value) {
        try {
            return this.setnx(key, value);
        } finally {
            this.close();
        }
    }

    public long strlen$(String key) {
        try {
            return this.strlen(key);
        } finally {
            this.close();
        }
    }

    public Object get$(String key) {
        try {
            return this.get(key);
        } finally {
            this.close();
        }
    }

    public <T>T get$(String key, Class<T> valueType) {
        try {
            return this.get(key, valueType);
        } finally {
            this.close();
        }
    }

    public <T>T get$(String key, TypeReference<T> valueType) {
        try {
            return this.get(key, valueType);
        } finally {
            this.close();
        }
    }

    public <T>T get$(String key, C<T> valueType) {
        try {
            return this.get(key, valueType);
        } finally {
            this.close();
        }
    }

    public Object getSet$(String key, Object newValue) {
        try {
            return this.getSet(key, newValue);
        } finally {
            this.close();
        }
    }

    public <T>T getSet$(String key, Object newValue, Class<T> valueType) {
        try {
            return this.getSet(key, newValue, valueType);
        } finally {
            this.close();
        }
    }

    public <T>T getSet$(String key, Object newValue, TypeReference<T> valueType) {
        try {
            return this.getSet(key, newValue, valueType);
        } finally {
            this.close();
        }
    }

    public <T>T getSet$(String key, Object newValue, C<T> valueType) {
        try {
            return this.getSet(key, newValue, valueType);
        } finally {
            this.close();
        }
    }


    public List<Object> mget$(String... keys) {
        try {
            return this.mget(keys);
        } finally {
            this.close();
        }
    }

    public <T> List<T> mget$(Class<T> valueType, String... keys) {
        try {
            return this.mget(valueType, keys);
        } finally {
            this.close();
        }
    }

    public <T> List<T> mget$(C<T> valueType, String... keys) {
        try {
            return this.mget(valueType, keys);
        } finally {
            this.close();
        }
    }


    public List<Object> mget$(Iterable<String> keys) {
        try {
            return this.mget(keys);
        } finally {
            this.close();
        }
    }

    public <T> List<T> mget$(Class<T> valueType, Iterable<String> keys) {
        try {
            return this.mget(valueType, keys);
        } finally {
            this.close();
        }
    }

    public <T> List<T> mget$(C<T> valueType, Iterable<String> keys) {
        try {
            return this.mget(valueType, keys);
        } finally {
            this.close();
        }
    }


    public Map<String, Object> mgetAsMap$(String... keys) {
        try {
            return this.mgetAsMap(keys);
        } finally {
            this.close();
        }
    }

    public Map<String, Object> mgetAsMap$(Iterable<String> keys) {
        try {
            return this.mgetAsMap(keys);
        } finally {
            this.close();
        }
    }

    public String mset$(Map<String, Object> keyAndValue) {
        try {
            return this.mset(keyAndValue);
        } finally {
            this.close();
        }
    }

    public String mset$(OwnPairs<String, Object> keyAndValue) {
        try {
            return this.mset(keyAndValue);
        } finally {
            this.close();
        }
    }

    public String mset$(Object javaBean) {
        try {
            return this.mset(javaBean);
        } finally {
            this.close();
        }
    }

    public String mset$(String jsonObject) {
        try {
            return this.mset(jsonObject);
        } finally {
            this.close();
        }
    }

    public ScanResult<String> scan$(String cursor) {
        try {
            return this.scan(cursor);
        } finally {
            this.close();
        }
    }

    public ScanResult<String> scan$(String cursor, ScanParams params) {
        try {
            return this.scan(cursor, params);
        } finally {
            this.close();
        }
    }

    public ScanResult<String> scan$(String cursor, ScanParams params, String type) {
        try {
            return this.scan(cursor, params, type);
        } finally {
            this.close();
        }
    }

    public Object eval$(String luaScript) {
        try {
            return this.eval(luaScript);
        } finally {
            this.close();
        }
    }

    public Object eval$(String luaScript, int keyCount, Object... args) {
        try {
            return this.eval(luaScript, keyCount, args);
        } finally {
            this.close();
        }
    }

    public Object eval$(String luaScript, int keyCount, List args) {
        try {
            return this.eval(luaScript, keyCount, args);
        } finally {
            this.close();
        }
    }


    public Object eval$(String luaScript, int keyCount, AbsOwnList args) {
        try {
            return this.eval(luaScript, keyCount, args);
        } finally {
            this.close();
        }
    }

    public Object eval$(String luaScript, List<String> keys, List args) {
        try {
            return this.eval(luaScript, keys, args);
        } finally {
            this.close();
        }
    }

    public Object eval$(String luaScript, String[] keys, Object... args) {
        try {
            return this.eval(luaScript, keys, args);
        } finally {
            this.close();
        }
    }

    public Object eval$(String luaScript, AbsOwnList<String> keys, AbsOwnList args) {
        try {
            return this.eval(luaScript, keys, args);
        } finally {
            this.close();
        }
    }
}
